package me.aartikov.alligator;


import me.aartikov.alligator.animations.AnimationData;
import me.aartikov.alligator.commands.*;
import me.aartikov.alligator.exceptions.NavigationException;
import me.aartikov.alligator.navigationfactories.NavigationFactory;
import me.aartikov.alligator.util.Log;
import ohos.aafwk.ability.Ability;
import ohos.eventhandler.EventRunner;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

//import me.aartikov.alligator.animations.AnimationData;
//import me.aartikov.alligator.commands.BackCommand;
//import me.aartikov.alligator.commands.BackToCommand;
//import me.aartikov.alligator.commands.Command;
//import me.aartikov.alligator.commands.FinishCommand;
//import me.aartikov.alligator.commands.ForwardCommand;
//import me.aartikov.alligator.commands.ReplaceCommand;
//import me.aartikov.alligator.commands.ResetCommand;
//import me.aartikov.alligator.commands.SwitchToCommand;
//import me.aartikov.alligator.exceptions.NavigationException;
//import me.aartikov.alligator.navigationfactories.NavigationFactory;


/**
 * Main library object. It translates calls of navigation methods to commands and puts them to a command queue.
 * Commands can be executed if a {@link NavigationContext} is bound, otherwise command execution will be delayed until a {@link NavigationContext} will be bound.
 */
public class OhosNavigator implements NavigationContextBinder, Navigator {
    private NavigationFactory mNavigationFactory;
    private NavigationContext mNavigationContext;
    private Queue<Command> mCommandQueue = new LinkedList<>();
    private boolean mIsExecutingCommands;
    private ScreenResolver mScreenResolver;
    private AbilityResultHandler mActivityResultHandler;

    public OhosNavigator(NavigationFactory navigationFactory) {
        mNavigationFactory = navigationFactory;
        mScreenResolver = new ScreenResolver(navigationFactory);
        mActivityResultHandler = new AbilityResultHandler(navigationFactory);
    }


    public NavigationFactory getNavigationFactory() {
        return mNavigationFactory;
    }

    public ScreenResolver getScreenResolver() {
        return mScreenResolver;
    }

    public AbilityResultHandler getActivityResultHandler() {
        return mActivityResultHandler;
    }

    @Override
    public NavigationContext getNavigationContext() {
        return mNavigationContext;
    }

    @Override
    public boolean isBound() {
        return mNavigationContext != null;
    }

    @Override
    public void bind(NavigationContext navigationContext) {
        checkThatMainThread();
//        if (mNavigationContext != null && mNavigationContext.getActivity() != navigationContext.getActivity()) {
//           Log.i("OhosNavigator.bind([navigationContext]):75");
//            return;
//        }
        mNavigationContext = navigationContext;
        mActivityResultHandler.setScreenResultListener(mNavigationContext.getScreenResultListener());
        executeQueuedCommands();
    }

    @Override
    public void unbind(Ability activity) {
        checkThatMainThread();
        if (mNavigationContext != null && mNavigationContext.getActivity() != activity) {
            return;
        }
        mActivityResultHandler.resetScreenResultListener();
        mNavigationContext = null;
    }

    @Override
    public boolean canExecuteCommandImmediately() {
        return mCommandQueue.isEmpty() && mNavigationContext != null;
    }

    @Override
    public boolean hasPendingCommands() {
        return !mCommandQueue.isEmpty();
    }

    /**
     * Adds a new screen and goes to it. Implemented with {@link ForwardCommand}.
     *
     * @param screen new screen
     */
    @Override
    public void goForward(Screen screen) {
        goForward(screen, null);
    }

    @Override
    public void goForward(Screen screen, AnimationData animationData) {
        Log.i("Navigator.goForward([screen, animationData]):113");
        executeCommand(new ForwardCommand(screen, animationData));
    }

    /**
     * Finishes a current screen and goes back to the previous screen. Implemented with {@link BackCommand}.
     * @return whether succeed
     */
    @Override
    public boolean goBack() {
        return goBack(null, null);
    }

    @Override
    public boolean goBack(Screen screen, AnimationData animationData) {
        return executeCommand(new BackCommand(screen, null, animationData));
    }

    /**
     * Finishes a current screen and goes back to the previous screen with result. Implemented with {@link BackCommand}.
     * @param screenResult  return parameter
     * @return whether succeed
     */
    @Override
    public boolean goBackWithResult(ScreenResult screenResult) {
        return goBackWithResult(null, screenResult, null);
    }

    @Override
    public boolean goBackWithResult(Screen screen, ScreenResult screenResult, AnimationData animationData) {
        return executeCommand(new BackCommand(screen, screenResult, animationData));
    }

    /**
     * Goes back to a screen with the given class. Implemented with {@link BackToCommand}.
     *
     * @param screenClass screen class for going back
     */
    @Override
    public void goBackTo(Class<? extends me.aartikov.alligator.Screen> screenClass) {
        goBackTo(screenClass, null);
    }

    @Override
    public void goBackTo(Class<? extends me.aartikov.alligator.Screen> screenClass, AnimationData animationData) {
        executeCommand(new BackToCommand(screenClass, null, animationData));
    }

    /**
     * Goes back to a screen with the given class and returns result to it. Implemented with {@link BackToCommand}.
     *
     * @param screenClass  screen class for going back
     * @param screenResult screen result that will be returned
     */
    @Override
    public void goBackToWithResult(Class<? extends Screen> screenClass, ScreenResult screenResult) {
        goBackToWithResult(screenClass, screenResult, null);
    }

    @Override
    public void goBackToWithResult(Class<? extends Screen> screenClass, ScreenResult screenResult, AnimationData animationData) {
        executeCommand(new BackToCommand(screenClass, screenResult, animationData));
    }

    /**
     * Replaces the last screen with a new screen. Implemented with {@link ReplaceCommand}.
     *
     * @param screen new screen
     */
    @Override
    public void replace(Screen screen) {
        replace(screen, null);
    }

    @Override
    public void replace(Screen screen, AnimationData animationData) {
        executeCommand(new ReplaceCommand(screen, animationData));
    }

    /**
     * Removes all other screens and adds a new screen. Implemented with {@link ResetCommand}.
     *
     * @param screen new screen
     */
    @Override
    public void reset(Screen screen) {
        reset(screen, null);
    }

    @Override
    public void reset(Screen screen, AnimationData animationData) {
        Log.i("Navigator.reset([screen, animationData]):202");
        executeCommand(new ResetCommand(screen, animationData));
    }

    /**
     * Finishes a current flow or a current top-level screen (represented by activity). Implemented with {@link FinishCommand}.
     */
    @Override
    public void finish() {
        finish(null);
    }

    @Override
    public void finish(Screen screen, AnimationData animationData) {
        executeCommand(new FinishCommand(null, null, false, animationData));
    }

    @Override
    public void finish(AnimationData animationData) {
        executeCommand(new FinishCommand(null, null, false, animationData));
    }

    /**
     * Finishes a current flow or a current top-level screen (represented by activity) with result. Implemented with {@link FinishCommand}.
     *
     * @param screenResult screen result that will be returned
     */
    @Override
    public void finishWithResult(ScreenResult screenResult) {
        finishWithResult(screenResult, null);
    }

    @Override
    public void finishWithResult(ScreenResult screenResult, AnimationData animationData) {
        executeCommand(new FinishCommand(null, screenResult, false, animationData));
    }

    /**
     * Finishes a current top-level screen (represented by activity). Implemented with {@link FinishCommand}.
     */
    @Override
    public void finishTopLevel() {
        finishTopLevel(null);
    }

    @Override
    public void finishTopLevel(AnimationData animationData) {
        executeCommand(new FinishCommand(null, null, true, animationData));
    }

    /**
     * Finishes a current top-level screen (represented by activity) with result. Implemented with {@link FinishCommand}.
     *
     * @param screenResult screen result that will be returned
     */
    @Override
    public void finishTopLevelWithResult(ScreenResult screenResult) {
        finishTopLevelWithResult(screenResult, null);
    }

    @Override
    public void finishTopLevelWithResult(ScreenResult screenResult, AnimationData animationData) {
        executeCommand(new FinishCommand(null, screenResult, true, animationData));
    }

    /**
     * Switches screen with a screen switcher. Implemented with {@link SwitchToCommand}.
     *
     * @param screen screen
     */
    @Override
    public void switchTo(Screen screen) {
        switchTo(screen, null);
    }

    @Override
    public void switchTo(Screen screen, AnimationData animationData) {
        executeCommand(new SwitchToCommand(screen, animationData));
    }

    /**
     * reduce screen with a screen switcher. Implemented with {@link SwitchToCommand}.
     *
     * @param screen screen
     */
    @Override
    public void reduce(Screen screen) {
        List<Screen> screens = new ArrayList<>();
        screens.add(screen);
        reduce(screens, null);
    }

    @Override
    public void reduce(List<Screen> screen) {
        executeCommand(new ReduceCommand(screen, null));
    }


    @Override
    public void reduce(List<Screen> screen, AnimationData animationData) {
        executeCommand(new ReduceCommand(screen, animationData));
    }


    protected boolean executeCommand(Command command) {
        checkThatMainThread();
        mCommandQueue.add(command);
        return executeQueuedCommands();
    }

    private boolean executeQueuedCommands() {
        if (mIsExecutingCommands) {
            return false;
        }
        boolean canExecuteCommands = false;
        mIsExecutingCommands = true;
        try {
            Log.i("OhosNavigator.executeQueuedCommands([]):288   " + (mNavigationContext != null));
            while (mNavigationContext != null && !mCommandQueue.isEmpty()) {
                Command command = mCommandQueue.remove();
                Log.i("OhosNavigator.executeQueuedCommands([]):292    " + command+"    "+mNavigationContext.getScreenSwitcher());
                canExecuteCommands = command.execute(mNavigationContext);
//                if (!canExecuteCommands) {
//                    mNavigationContext = null;
//                }
            }
        } catch (NavigationException e) {
            mCommandQueue.clear();
            mNavigationContext.getNavigationErrorListener().onNavigationError(e);
        } catch (Exception e) {
            mCommandQueue.clear();
            throw e;
        } finally {
            mIsExecutingCommands = false;
        }
        return canExecuteCommands;
    }

    private void checkThatMainThread() {
        if (Thread.currentThread().getId() != EventRunner.getMainEventRunner().getThreadId()) {
            throw new RuntimeException("Can only be called from the main thread.");
        }
    }

}